namespace ClassyBlog.Infrastructure.Specs.Unit
{
    using System;
    using System.Collections.Generic;
    using System.Dynamic;
    using System.Linq.Expressions;

    using Machine.Specifications;
    using NSubstitute;

    [Subject(typeof(DynamicPropertiesMetaObject))]
    public class when_trying_to_get_member_binder_with_null_binder
    {
        static Exception exception;
        static DynamicPropertiesMetaObject metaObject;

        Establish context = () =>
            metaObject = new DynamicPropertiesMetaObject(
                Substitute.For<Expression>(),
                Substitute.For<IHasDynamicProperties>());

        Because of = () => exception =
            Catch.Exception(() => metaObject.BindGetMember(null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(DynamicPropertiesMetaObject))]
    public class when_trying_to_set_member_binder_with_null_value
    {
        static Exception exception;
        static DynamicPropertiesMetaObject metaObject;

        Establish context = () =>
            metaObject = new DynamicPropertiesMetaObject(
                Substitute.For<Expression>(),
                Substitute.For<IHasDynamicProperties>());

        Because of = () => exception =
            Catch.Exception(() => metaObject.BindSetMember(
                Substitute.For<SetMemberBinder>(
                new object[]
                    {
                        "foo",
                        false
                    }),
                null));

        It should_throw_exception = () =>
            exception.ShouldBeOfType<ArgumentNullException>();
    }

    [Subject(typeof(DynamicPropertiesMetaObject))]
    public class when_setting_dynamic_property_of_an_object
    {
        static DummyObject dummyObject;
        static dynamic dynamicDummyObject;

        static string property;

        Establish context = () =>
        {
            dummyObject = new DummyObject();

            dynamicDummyObject = dummyObject;
            dynamicDummyObject.DummyProperty = "DynamicValue";
        };

        Because of = () => property = dynamicDummyObject.DummyProperty;

        It should_be_able_to_get = () => property.ShouldEqual("DynamicValue");
    }

    [Subject(typeof(DynamicPropertiesMetaObject))]
    public class when_getting_regular_property_as_dynamic_property_of_an_object
    {
        static DummyObject dummyObject;
        static dynamic dynamicDummyObject;
        static string foo;

        Establish context = () =>
        {
            dummyObject = new DummyObject { Foo = "Bar" };
            dynamicDummyObject = dummyObject;
        };

        Because of = () => foo = dynamicDummyObject.Foo;

        It should_be_able_to_get = () => foo.ShouldEqual("Bar");
    }

    [Subject(typeof(DynamicPropertiesMetaObject))]
    public class when_setting_regular_property_as_dynamic_property_of_an_object
    {
        static DummyObject dummyObject;
        static dynamic dynamicDummyObject;

        Establish context = () =>
        {
            dummyObject = new DummyObject();
            dynamicDummyObject = dummyObject;
        };

        Because of = () => dynamicDummyObject.Foo = "Bar";

        It should_be_able_to_set = () => dummyObject.Foo.ShouldEqual("Bar");
    }

    public class DummyObject : IHasDynamicProperties
    {
        public DummyObject()
        {
            DynamicProperties = new Dictionary<string, object>();
        }

        public string Foo { get; set; }

        public IEnumerable<string> DynamicPropertyNames
        {
            get { return DynamicProperties.Keys; }
        }

        protected IDictionary<string, object> DynamicProperties
        {
            get; private set;
        }

        public virtual DynamicMetaObject GetMetaObject(Expression parameter)
        {
            return new DynamicPropertiesMetaObject(parameter, this);
        }

        public virtual object GetDynamicProperty(string name)
        {
            object value;

            return DynamicProperties.TryGetValue(name, out value) ?
                   value :
                   null;
        }

        public virtual void SetDynamicProperty(string name, object value)
        {
            DynamicProperties[name] = value;
        }
    }
}